package cn.edu.hust.cm.lib;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * IO操作工具集
 * 
 * @since 2012-08-11
 * @author 陈明
 * @version 1.0
 * 
 */
public class IOUtils {
	public static final boolean DEBUG = true;

	/**
	 * 从输入流中拷贝count个字节到输出流中.
	 * 
	 * @param from
	 *            输入流
	 * @param to
	 *            输出流
	 * @param count
	 *            期望拷贝的字节数
	 * @return 实际拷贝的字节数
	 * @throws IOException
	 *             从from中读取数据出错或向to中写入数据出错
	 */
	public static long copy(final InputStream from, final OutputStream to,
			long count) throws IOException {
		if (null == from || null == to) {
			throw new IllegalArgumentException(
					"input is null or output is null");
		}
		if (count < 0) {
			throw new IllegalArgumentException("count must >= 0");
		}
		if (0 == count) {
			return 0;
		}

		long copied = 0;
		final long total = count;

		byte[] buf = new byte[1024 * 8];
		int len = -1;

		// XXX 危险!!! 可能影响from这个流(读取过多的字节并且抛弃)
		// while (-1 != (len = from.read(buf))) {
		// if (copied + len > total) {
		// len = (int) (total - copied);
		// }
		//
		// to.write(buf, 0, len);
		// copied += len;
		// }

		if (total < buf.length) {
			copied = from.read(buf, 0, (int) total);
			if (-1 != copied) {
				to.write(buf, 0, (int) copied);
			} else {
				copied = 0;
			}
		} else {
			int wantRead = buf.length;
			while (-1 != (len = from.read(buf, 0, wantRead))) {
				to.write(buf, 0, len);
				copied += len;

				int left = (int) (total - copied);
				if (left < wantRead) {
					wantRead = left;
				}
			}
		}

		to.flush();

		return copied;
	}

	/**
	 * 关闭一个输入流或输出流
	 * 
	 * @param stream
	 *            输入流或输出流
	 * @return 成功关闭流则返回true, 否则返回false
	 * @see IOUtils#close(Closeable...)
	 */
	public static boolean close(Closeable stream) {
		if (null != stream) {
			try {
				stream.close();
				return true;
			} catch (IOException e) {
				System.err.println(e.getMessage());
			}
			return false;
		}
		return true;
	}

	/**
	 * 关闭一组输入流或输出流
	 * 
	 * @param streams
	 *            一组输入流或输出流
	 * @see IOUtils#close(Closeable)
	 */
	public static void close(Closeable... streams) {
		for (Closeable closeable : streams) {
			try {
				closeable.close();
			} catch (IOException e) {
				System.err.println(e.getMessage());
			}
		}
	}

	/**
	 * 从一个输入流逐个读出字节, 直到遇到结束(遇到-1)
	 * 
	 * @param in
	 *            输入流
	 * @return 字节数组
	 * @throws IOException
	 */
	public static byte[] readBytes(InputStream in) throws IOException {
		List<Byte> list = new ArrayList<Byte>();
		BufferedInputStream bis = new BufferedInputStream(in);
		int i = -1;
		while (-1 != (i = bis.read())) {
			list.add((byte) i);
		}
		return CollectionsExt.collectionToArray(list);
	}

	/**
	 * 从一个输入流逐个读出字节, 直到遇到结束(遇到-1)或字节总数超过limit
	 * 
	 * @param in
	 *            输入流
	 * @return 字节数组
	 * @throws IOException
	 */
	public static byte[] readBytes(InputStream in, int limit) {
		// TODO
		return null;
	}

	/**
	 * 从一个输入流逐个读出字节, 直到遇到结束(遇到-1)或将buf填满
	 * 
	 * @param in
	 *            输入流
	 * @param buf
	 *            缓冲区
	 * @return 字节数组
	 * @throws IOException
	 */
	public static byte[] readBytes(InputStream in, byte[] buf) {
		// TODO
		return null;
	}

	/**
	 * 从in中读出全部字符串
	 * 
	 * @param in
	 *            输入流
	 * @return 包含读取的字符串的List
	 * @see IOUtils#readLines(BufferedReader, int)
	 * @throws IOException
	 */
	// TODO 提供 公开的 限制行数的方法
	public static List<String> readLines(InputStream in) throws IOException {
		return readLines0(in, Integer.MAX_VALUE);
	}

	// XXX 提供行数作为参数可能是个糟糕的想法, 比如一个流中有10行, 第一次读1行, 第二次读2行, ...
	// XXX 该怎么实现?? 目前这个实现是存在问题的. 所以不提供 公开的 限制行数的方法
	private static List<String> readLines0(InputStream in, int limit)
			throws IOException {
		List<String> all = new ArrayList<String>();

		if (null != in) {
			BufferedReader reader = new BufferedReader(
					new InputStreamReader(in));
			int count = 0;
			String line = null;
			while (count < limit && null != (line = reader.readLine())) {
				all.add(line);
				++count;
			}
		}
		return all;
	}

	/**
	 * 从reader中读出limit行字符串
	 * 
	 * @param reader
	 *            输入reader
	 * @param limit
	 *            行数限制
	 * @return 包含读取的字符串的List
	 * @see IOUtils#readLines(InputStream)
	 * @throws IOException
	 */
	// XXX readLines(InputStream in) 在这个场景下使用存在问题: 一个流中有10行, 第一次读1行, 第二次读2行, ...
	// XXX 提供本方法作为补充
	public static List<String> readLines(BufferedReader reader, int limit)
			throws IOException {
		List<String> all = new ArrayList<String>();

		if (null != reader) {
			int count = 0;
			String line = null;
			while (count < limit && null != (line = reader.readLine())) {
				all.add(line);
				++count;
			}
		}
		return all;
	}

	/**
	 * 执行一个本地进程, 只关心是否执行成功, 不关心输出结果
	 * 
	 * @param args
	 *            命令
	 * @return true如果执行成功, 否则false
	 * @see IOUtils#execute(String...)
	 */
	public static boolean execute2(String... args) {
		ProcessBuilder builder = new ProcessBuilder(args);
		Process process = null;
		try {
			process = builder.start();
			process.waitFor();

			if (true) {
				System.err.println(IOUtils.readLines(process.getErrorStream()));
			}
			return 0 == process.exitValue();
		} catch (IOException e) {
			JavaLog.e(e.getMessage());
		} catch (InterruptedException e) {
			JavaLog.e(e.getMessage());
		} finally {
			// TODO 貌似没有必要关闭
			if (null != process) {
				IOUtils.close(process.getErrorStream());
				IOUtils.close(process.getInputStream());
				IOUtils.close(process.getOutputStream());
			}
		}
		return false;
	}

	/**
	 * 执行一个本地进程
	 * 
	 * @param args
	 *            命令
	 * @return 命令输出结果, 形式为字符串list的输出流
	 * @see IOUtils#execute2(String...)
	 */
	public static List<String> execute(String... args) {
		List<String> res = null;
		ProcessBuilder pb = new ProcessBuilder(args);

		Process process = null;
		try {
			process = pb.start();
			process.waitFor();

			if (true) {
				System.err.println(IOUtils.readLines(process.getErrorStream()));
			}

			res = IOUtils.readLines(process.getInputStream());

			if (0 != process.exitValue()) {
				res.clear();
			}
		} catch (IOException e) {
			JavaLog.e(e.getMessage());
		} catch (InterruptedException e) {
			JavaLog.e(e.getMessage());
		} finally {
			// TODO 貌似没有必要关闭
			if (null != process) {
				IOUtils.close(process.getErrorStream());
				IOUtils.close(process.getInputStream());
				IOUtils.close(process.getOutputStream());

			}
		}
		return null == res ? new ArrayList<String>(0) : res;
	}

	/**
	 * 通过配置文件名获取属性表
	 * 
	 * @param dir
	 *            根路径
	 * @param fileName
	 *            配置文件名
	 * @return 配置文件对应的属性表
	 */
	public static Properties getProperties(String dir, String fileName) {
		// 如果dir为null, 则置为根目录
		if (null == dir) {
			dir = "/";
		}

		Properties prop = new Properties();
		InputStream in = IOUtils.class.getResourceAsStream(dir + fileName);
		if (null != in) {
			in = new BufferedInputStream(in);
			try {
				prop.load(in);
			} catch (IOException e) {
				JavaLog.e(e.getMessage());
			} finally {
				IOUtils.close(in);
			}
		} else {
			JavaLog.e(dir + fileName + " not exists");
		}

		return prop;
	}

	/**
	 * 将int整形转换成 byte数组, 数组为big endian序
	 * 
	 * @param i
	 *            待转换的整数
	 * @return 一个长度为4的byte数组
	 * @see IOUtils#intToBytesFast(int)
	 */
	public static byte[] intToBytes(int n) {
		// 可以确定int大小 为4个字节
		ByteArrayOutputStream bos = new ByteArrayOutputStream(4);
		DataOutputStream dos = new DataOutputStream(bos);
		byte[] ret = null;
		try {
			dos.writeInt(n);
			dos.flush();
			ret = bos.toByteArray();
		} catch (Exception e) {
			JavaLog.e(e.getMessage());
		} finally {
			IOUtils.close(dos);
		}
		return null == ret ? new byte[4] : ret;
	}

	/**
	 * 将int整形转换成 byte数组, 数组为big endian序
	 * 
	 * @param i
	 *            待转换的整数
	 * @return 一个长度为4的byte数组
	 * @see IOUtils#intToBytes(int)
	 */
	// TODO 不使用ByteArrayOutputStream会不会快一些呢???
	public static byte[] intToBytesFast(int n) {
		int tmp = n;
		byte[] ret = new byte[4];
		for (int i = ret.length - 1; i > -1; i--) {
			ret[i] = (byte) (tmp & 0xff);
			tmp = tmp >> 8;
		}
		return ret;
	}

	/**
	 * 将长度为4的byte数组转换成一个int整数
	 * 
	 * @param b
	 *            待转换的长度为4的byte数组
	 * @return 一个int整数
	 * @see IOUtils#intToBytes(int)
	 * @see IOUtils#intToBytesFast(int)
	 */
	public static int bytesToInt(byte[] b) {
		if (null == b || 4 != b.length) {
			throw new IllegalArgumentException();
		}

		int ret = 0;
		for (int i = 0; i < b.length; i++) {
			ret += b[i];
			if (b[i] < 0) {
				ret += 256;
			}

			if (i == b.length - 1) {
				break;
			} else {
				ret *= 256;
			}
		}
		return ret;
	}

	/**
	 * 交换一个int整数的二进制形式中的相邻奇偶位. 这个功能不常用, 来自一个笔试题
	 * 
	 * @param n
	 *            待转换的int值
	 * @return 交换 相邻奇偶位后这个二进制串代表的int值
	 */
	public static int swapBits(int n) {
		int value = (n & 0XAAAA) >> 1 | (n & 0X5555) << 1;
		return value;
	}
}
